﻿//+-------------------------------------------------------------------------------------------------------------------------------------------------------
//
//Description: DeployLandingPageViewModel
//Copyright © Microsoft Corporation
//Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. 
//You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 
//
//THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, 
//INCLUDING WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABLITY OR NON-INFRINGEMENT. 
//See the Apache Version 2.0 License for specific language governing permissions and limitations under the License.
// 
//--------------------------------------------------------------------------------------------------------------------------------------------------------
using System;
using System.Collections.ObjectModel;
using System.Linq;
using System.Net;
using System.Threading;
using System.Windows;
using System.Windows.Input;
using System.Configuration;
using System.Collections.Generic;
using System.ComponentModel;
using System.Xml.Linq;
using System.Security.Cryptography.X509Certificates;
using Microsoft.WindowsAzure.Storage;
using Microsoft.WindowsAzure.Storage.Auth;
using Microsoft.WindowsAzure.Storage.Blob;

namespace VmroleAuthor.ViewModels
{
    public class DeployLandingPageViewModel : DeployPageViewModelBase, IDataErrorInfo
    {
        private List<CloudService> _services;
        private List<StorageAccount> _storageAccounts;
        private List<VirtualMachine> _vms;
        private List<StorageContainer> _storageContainers;
        private string _waitMessage = string.Empty;

        // tracks whether fields have failed to be populated
        private bool _failedToConnectToCloudService;
        private bool _failedToConnectToStorageAccount;
        private bool _failedToConnectToVirtualMachine;
        private bool _failedToConnectToStorageContainer;

        // tracks whether the queries have ever completed for the fields
        private bool _servicesCallBackCompleted;
        private bool _vmsCallBackCompleted;
        private bool _storageAccountsCallBackCompleted;
        private bool _storageContainersCallBackCompleted;

        // lock for wait message
        private static readonly ReaderWriterLock RwWmLock = new ReaderWriterLock();

        // locks for VMs
        private static readonly ReaderWriterLock RwvMsLock = new ReaderWriterLock();
        private static readonly ReaderWriterLock RwSvmLock = new ReaderWriterLock();
        
        // locks for storage containers
        private static readonly ReaderWriterLock StorageContainerReaderWriterLock = new ReaderWriterLock();
        private static readonly ReaderWriterLock RwSscLock = new ReaderWriterLock();

        // locks for services
        private static readonly ReaderWriterLock RwSsLock = new ReaderWriterLock();
        private static readonly ReaderWriterLock RwScsLock = new ReaderWriterLock();

        // locks for storage accounts
        private static readonly ReaderWriterLock StorageAccountsReaderWriterLock = new ReaderWriterLock();
        private static readonly ReaderWriterLock SaReaderWriterLock = new ReaderWriterLock();

        private const int RwTimeoutinMilliSeconds = 30000;

        private const string AzureWaitMessage = " Please wait while the tool connects to Microsoft Azure . ";

        public DeployLandingPageViewModel(VmRoleResourceExtensionViewModel resExtViewModel,
            ResExtAzureDeploymentData deployData)
            : base(resExtViewModel, deployData)
        {
            this._services = new List<CloudService>();
            this._vms = new List<VirtualMachine>();
            this._storageAccounts = new List<StorageAccount>();
            this._storageContainers = new List<StorageContainer>();
            this.PageTitle = "DEPLOYMENT CONFIGURATION";

            // check if the subscription and thumbprint have already been set
            // this can happen if the values were input through the config file
            if (this.DeployData.CanConnect())
            {
                // invoke the callers to set the remaining values 
                this._failedToConnectToCloudService = false;
                this._failedToConnectToStorageAccount = false;
                this.CalculateDefaults();
            }
        }

        public static class PropertyNames
        {
            public const string SubscriptionId = "SubscriptionId";
            public const string MgtCertThumbprint = "MgtCertThumbprint";
            public const string CloudServices = "CloudServices";
            public const string CloudStorageAccounts = "CloudStorageAccounts";
            public const string SelectedCloudService = "SelectedCloudService";
            public const string SelectedStorageAccount = "SelectedStorageAccount";
            public const string VirtualMachines = "VirtualMachines";
            public const string CloudStorageContainers = "CloudStorageContainers";
            public const string SelectedVirtualMachine = "SelectedVirtualMachine";
            public const string SelectedStorageContainer = "SelectedStorageContainer";
            public const string WaitMessage = "WaitMessage";
            public const string WaitMessageVisibility = "WaitMessageVisibility";
            public const string IsUserInputComplete = "IsUserInputComplete";
            public const string AreResourcesUploaded = "AreResourcesUploaded";
            public const string DeleteResourcesAfterDeployment = "DeleteResourcesAfterDeployment";
            public const string PayloadVisibility = "PayloadVisibility";
        }

        public string SubscriptionId
        {
            get
            {
                return this.DeployData.SubscriptionId;
            }
            set
            {
                this.DeployData.SubscriptionId = value;
                this._failedToConnectToCloudService = false;
                this._failedToConnectToStorageAccount = false;
                this.OnPropertyChanged(PropertyNames.SubscriptionId);
                this.OnPropertyChanged(PropertyNames.MgtCertThumbprint);
                this.CalculateDefaults();   
            }
        }

        public string MgtCertThumbprint
        {
            get
            {
                return this.DeployData.MgtCertThumbprint;
            }
            set
            {
                this.DeployData.MgtCertThumbprint = value.Trim();
                X509Certificate2 cert = AzureHelper.GetCertificate(this.DeployData.MgtCertThumbprint);
                this._failedToConnectToCloudService = false;
                this._failedToConnectToStorageAccount = false;
                this.OnPropertyChanged(PropertyNames.MgtCertThumbprint);
                this.DeployData.MgmtCertificate = cert;
                this.CalculateDefaults();
            }
        }

        private void ClearValues()
        {
            this._services.Clear();
            this.SelectedCloudService = null;
            this._storageAccounts.Clear();
            this.SelectedStorageAccount = null;
            this.SelectedVirtualMachine = null;
            this.SelectedStorageContainer = null;
            this.UpdateWaitMessage(string.Empty);
        }

        private void CallAllOnPropertyChanged()
        {
            this.OnPropertyChanged(PropertyNames.CloudServices);
            this.OnPropertyChanged(PropertyNames.CloudStorageAccounts);
            this.OnPropertyChanged(PropertyNames.SelectedCloudService);
            this.OnPropertyChanged(PropertyNames.SelectedStorageAccount);
            this.OnPropertyChanged(PropertyNames.VirtualMachines);
            this.OnPropertyChanged(PropertyNames.SelectedVirtualMachine);
            this.OnPropertyChanged(PropertyNames.CloudStorageContainers);
            this.OnPropertyChanged(PropertyNames.SelectedStorageContainer);
            this.OnPropertyChanged(PropertyNames.WaitMessage);
            this.OnPropertyChanged(PropertyNames.WaitMessageVisibility);
            this.OnPropertyChanged(PropertyNames.IsUserInputComplete);
            this.OnPropertyChanged(PropertyNames.AreResourcesUploaded);
            this.OnPropertyChanged(PropertyNames.DeleteResourcesAfterDeployment);
            this.OnPropertyChanged(PropertyNames.PayloadVisibility);
        }

        public Visibility PayloadVisibility
        {
            get
            {
                return (this.DeployData.HasPayload ? Visibility.Visible : Visibility.Hidden);
            }
        }

        public bool AreResourcesUploaded
        {
            get
            {
                return this.DeployData.AreResourcesUploaded;
            }
            set
            {
                this.DeployData.AreResourcesUploaded = value;
                this.OnPropertyChanged(PropertyNames.AreResourcesUploaded);
            }
        }

        public bool DeleteResourcesAfterDeployment
        {
            get
            {
                return this.DeployData.DeleteResourcesAfterDeployment;
            }
            set
            {
                this.DeployData.DeleteResourcesAfterDeployment = value;
                this.OnPropertyChanged(PropertyNames.DeleteResourcesAfterDeployment);
            }
        }

        public string WaitMessage
        {
            get
            {
                try
                {
                    RwWmLock.AcquireReaderLock(RwTimeoutinMilliSeconds);
                    try
                    {
                        return this._waitMessage;
                    }
                    finally
                    {
                        RwWmLock.ReleaseReaderLock();
                    }
                }
                catch (ApplicationException)
                {
                    // log this
                }
                return string.Empty;
            }
        }

        public object WaitMessageVisibility
        {
            get
            {
                return (string.IsNullOrEmpty(this.WaitMessage)  ? Visibility.Hidden : Visibility.Visible);
            }
        }

        private void UpdateWaitMessage(string updateMessage = null)
        {
            try
            {
                RwWmLock.AcquireWriterLock(RwTimeoutinMilliSeconds);
                try
                {
                    if (updateMessage == null)
                    {
                        // a . update mg is called
                        if (string.IsNullOrEmpty(this._waitMessage))
                        {
                            this._waitMessage = AzureWaitMessage;
                        }
                        else
                        {
                            this._waitMessage = this._waitMessage + " . ";
                        }
                    }
                    else if (updateMessage == string.Empty)
                    {
                        // a clear wait mg is called
                        this._waitMessage = updateMessage;
                    }
                    else
                    {
                        // a new wait mg is called
                        if (string.IsNullOrEmpty(this._waitMessage))
                        {
                            this._waitMessage = updateMessage;
                        }
                        else
                        {
                            this._waitMessage = this._waitMessage + " . ";
                        }
                    }
                }
                finally
                {
                    RwWmLock.ReleaseWriterLock();
                }
            }
            catch (ApplicationException)
            {
                // log this
            }

            if (updateMessage == string.Empty)
            {
                this.OnPropertyChanged(PropertyNames.IsUserInputComplete);
            }
            this.OnPropertyChanged(PropertyNames.WaitMessage);
            this.OnPropertyChanged(PropertyNames.WaitMessageVisibility);
        }

        private List<CloudService> Services
        {
            get 
            {
                try
                {
                    RwSsLock.AcquireReaderLock(RwTimeoutinMilliSeconds);
                    try
                    {
                        return this._services;
                    }
                    finally
                    {
                        RwSsLock.ReleaseReaderLock();
                    }
                }
                catch (ApplicationException)
                { }
                return new List<CloudService>();
            }
            set 
            {
                try
                {
                    RwSsLock.AcquireWriterLock(RwTimeoutinMilliSeconds);
                    try
                    {
                        this._services = value;
                    }
                    finally
                    {
                        RwSsLock.ReleaseWriterLock();
                    }
                }
                catch (ApplicationException)
                { }
                this.OnPropertyChanged(PropertyNames.CloudServices);
            }
        }

        private void CalculateDefaults()
        {
            if (this.DeployData.CanConnect())
            {
                try
                {
                    // update the AppSettings with new values for subscription id and cert thumbprint
                    Configuration configuration = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
                    string configSubId = null;
                    string configCertThumbprint = null;
                    // check whether the values exist
                    if (configuration.AppSettings.Settings.AllKeys.Contains(AzureHelper.SubscriptionId))
                    {
                        configSubId = configuration.AppSettings.Settings[AzureHelper.SubscriptionId].Value;
                    }
                    if (configuration.AppSettings.Settings.AllKeys.Contains(AzureHelper.MgtCertThumbprint))
                    {
                        configCertThumbprint = configuration.AppSettings.Settings[AzureHelper.MgtCertThumbprint].Value;
                    }

                    bool saveConfig = false;
                    if (!AzureHelper.StringIsEqual(configSubId, this.SubscriptionId))
                    {
                         // subscription id
                        if (!string.IsNullOrEmpty(configSubId))
                        {
                            configuration.AppSettings.Settings.Remove(AzureHelper.SubscriptionId);
                        }
                        configuration.AppSettings.Settings.Add(AzureHelper.SubscriptionId, this.SubscriptionId);
                        saveConfig = true;
                    }
                    
                    if (!AzureHelper.StringIsEqual(configCertThumbprint, this.MgtCertThumbprint))
                    {
                        // cert thumbrint
                        if (!string.IsNullOrEmpty(configCertThumbprint))
                        {
                            configuration.AppSettings.Settings.Remove(AzureHelper.MgtCertThumbprint);
                        }
                        configuration.AppSettings.Settings.Add(AzureHelper.MgtCertThumbprint, this.MgtCertThumbprint);
                        saveConfig = true;
                    }

                    if (saveConfig)
                    {
                        // save it to the file
                        configuration.Save();
                    }
                }
                catch (Exception )
                {
                    // log
                }

                // check to see whether the MS EAH version is know
                if (string.IsNullOrEmpty(AzureHelper.MSExtAppHandlerVersion))
                {
                    // need to query Azure for the list of EAH Versions
                    ThreadPool.QueueUserWorkItem(this.CalculateMSEAHVersionCallBack, this.DeployData);
                }

                // get the Services
                ThreadPool.QueueUserWorkItem(this.CalculateServicesCallBack, this.DeployData);

                // get the Storage Accounts
                ThreadPool.QueueUserWorkItem(this.CalculateStorageAccountsCallBack, this.DeployData);
            }
            else
            {
                this.ClearValues();
                this.UpdateWaitMessage(string.Empty);
                this.CallAllOnPropertyChanged();
            }   
        }

        /// <summary>
        /// calculate the MS EAH Version to use
        /// </summary>
        /// <param name="context"></param>
        private void CalculateMSEAHVersionCallBack(Object context)
        {
            if (this.DeployData.CanConnect())
            {
                this.UpdateWaitMessage(DeployLandingPageViewModel.AzureWaitMessage);

                string uriFormat = "https://{0}/{1}/services/resourceextensions/{2}/{3}";
                Uri uri = new Uri(String.Format(uriFormat, this.DeployData.FrontEnd, this.SubscriptionId, AzureHelper.MSExtAppHandlerPublisherName, AzureHelper.MSExtAppHandlerName));
                // Submit the request and get the response
                XDocument responseBody;
                HttpWebResponse response = AzureHelper.MakeRestRequest(uri, "GET", this.DeployData.MgmtCertificate, out responseBody);
                this.UpdateWaitMessage();

                if (response!= null && response.StatusCode == HttpStatusCode.OK)
                {
                    AzureHelper.SetMSExtAppHandlerVersion(responseBody);
                }
                else
                {
                    AzureHelper.MSExtAppHandlerVersion = string.Empty;
                    string msg = "The tool could not locate the Extension Handler to use. Please verify the Extension Handler settings in the tool's config file and restart the deployment";
                    MessageBox.Show(msg, DefinitionViewModel.ToolHeader, MessageBoxButton.OK, MessageBoxImage.Exclamation);
                }

                // clear the message
                this.UpdateWaitMessage(string.Empty);
            }
            else
            {
                AzureHelper.MSExtAppHandlerVersion = string.Empty;
            }

            // signal the ui thread
            this.CurrentDispatcher.Invoke((ThreadStart)delegate
            {
                CallInvalidateRequerySuggested();
            });
            this.OnPropertyChanged(PropertyNames.IsUserInputComplete);

        }

        /// <summary>
        /// calculate the list of services
        /// </summary>
        /// <param name="context"></param>
        private void CalculateServicesCallBack(Object context)
        {
            if (this.DeployData.CanConnect())
            {
                this.UpdateWaitMessage(DeployLandingPageViewModel.AzureWaitMessage);

                // cloud services & selected cloud service
                string uriFormat = "https://{0}/{1}/services/hostedservices";
                Uri uri = new Uri(String.Format(uriFormat, this.DeployData.FrontEnd, this.SubscriptionId));         
                this.UpdateWaitMessage();

                // Submit the request and get the response
                XDocument responseBody;
                HttpWebResponse response = AzureHelper.MakeRestRequest(uri, "GET", this.DeployData.MgmtCertificate, out responseBody);
                this.UpdateWaitMessage();

                if (response != null && response.StatusCode == HttpStatusCode.OK)
                {
                    this.Services = AzureHelper.GetCloudServices(responseBody);
                    this.SelectedCloudService = this._services.Count > 0 ? this._services[0] : null;
                    this._failedToConnectToCloudService = false;
                    this.UpdateWaitMessage();
                }
                else
                {
                    this._failedToConnectToCloudService = true;
                    this.ClearValues();
                }
                this._servicesCallBackCompleted = true;
            }
            else
            {
                this.ClearValues();
            }
            
            this.UpdateWaitMessage(string.Empty);
            this.CallAllOnPropertyChanged();
        }

        /// <summary>
        /// calculate the list storage accounts
        /// </summary>
        /// <param name="context"></param>
        private void CalculateStorageAccountsCallBack(Object context)
        {
            if (!this.DeployData.HasPayload)
            {
                // no payload return
                return;
            }

            if (this.DeployData.CanConnect())
            {
                this.UpdateWaitMessage(DeployLandingPageViewModel.AzureWaitMessage);

                // storage accounts & selected storage account
                string stUriFormat = "https://{0}/{1}/services/storageservices";
                Uri stUri = new Uri(String.Format(stUriFormat, this.DeployData.FrontEnd, this.SubscriptionId));

                // Submit the request and get the response
                XDocument stResponseBody;
                HttpWebResponse stResponse = AzureHelper.MakeRestRequest(stUri, "GET", this.DeployData.MgmtCertificate, out stResponseBody);
                this.UpdateWaitMessage();

                if (stResponse != null && stResponse.StatusCode == HttpStatusCode.OK)
                {
                    try
                    {
                        this.StorageAccounts = AzureHelper.GetStorageAccounts(stResponseBody, this.DeployData);
                        this.SelectedStorageAccount = this._storageAccounts.Count > 0 ? this._storageAccounts[0] : null;
                        this._failedToConnectToStorageAccount = false;
                        this.UpdateWaitMessage();
                    }
                    catch (Exception)
                    {
                        this._failedToConnectToStorageAccount = true;
                        this.ClearValues();
                    }
                }
                else
                {
                    this._failedToConnectToStorageAccount = true;
                    this.ClearValues();
                }

                this._storageAccountsCallBackCompleted = true;
            }
            else
            {
                this.ClearValues();
            }

            this.UpdateWaitMessage(string.Empty);
            this.CallAllOnPropertyChanged();
        }

        public ObservableCollection<CloudService> CloudServices
        {
            get
            {
                return new ObservableCollection<CloudService>(this.Services);
            }
        }
  
        public CloudService SelectedCloudService
        {
            get
            {
                try
                {
                    RwScsLock.AcquireReaderLock(RwTimeoutinMilliSeconds);
                    try
                    {
                        return this.DeployData.SelectedCloudService;
                    }
                    finally
                    {
                        RwScsLock.ReleaseReaderLock();
                    }
                }
                catch (ApplicationException)
                { }
                return null;
            }
            set
            {
                try
                {
                    RwScsLock.AcquireWriterLock(RwTimeoutinMilliSeconds);
                    try
                    {
                        this.DeployData.SelectedCloudService = value;
                    }
                    finally
                    {
                        RwScsLock.ReleaseWriterLock();
                    }
                }
                catch (ApplicationException)
                {
                    // return as there was an exception acquiring the lock
                    return;
                }
                ThreadPool.QueueUserWorkItem(this.SelectCloudServiceCallBack, this.DeployData);
                this.OnPropertyChanged(PropertyNames.SelectedCloudService);   
            }
        }

        private void SelectCloudServiceCallBack(Object context)
        {
            if (this.DeployData.SelectedCloudService != null)
            {
                this.UpdateWaitMessage(DeployLandingPageViewModel.AzureWaitMessage);

                string uriFormat = "https://{0}/{1}/services/hostedservices/{2}?embed-detail=true";
                Uri uri = new Uri(String.Format(uriFormat, this.DeployData.FrontEnd, this.SubscriptionId, this.SelectedCloudService.Name));

                // Submit the request and get the response
                XDocument responseBody;
                HttpWebResponse response = AzureHelper.MakeRestRequest(uri, "GET", this.DeployData.MgmtCertificate, out responseBody);
           
                this.UpdateWaitMessage();

                if (response != null && response.StatusCode == HttpStatusCode.OK)
                {
                    this.VMs = AzureHelper.GetVirtualMachines(responseBody);
                    this.SelectedVirtualMachine = this._vms.Count > 0 ? this._vms[0] : null;
                    this._failedToConnectToVirtualMachine = false;
                    
                    this.UpdateWaitMessage();
                }
                else
                {
                    this._failedToConnectToVirtualMachine = true;
                    this._vms.Clear();
                    this.SelectedVirtualMachine = null;
                }

                this._vmsCallBackCompleted = true;
            }

            // clear the message
            this.UpdateWaitMessage(string.Empty);

            this.OnPropertyChanged(PropertyNames.SelectedCloudService);
            this.OnPropertyChanged(PropertyNames.VirtualMachines);
            this.OnPropertyChanged(PropertyNames.SelectedVirtualMachine);
            this.OnPropertyChanged(PropertyNames.IsUserInputComplete);
        }

        private List<VirtualMachine> VMs
        {
            get
            {
                try
                {
                    RwvMsLock.AcquireReaderLock(RwTimeoutinMilliSeconds);
                    try
                    {
                        return this._vms;
                    }
                    finally
                    {
                        RwvMsLock.ReleaseReaderLock();
                    }
                }
                catch (ApplicationException)
                { }
                return new List<VirtualMachine>();
            }
            set
            {
                try
                {
                    RwvMsLock.AcquireWriterLock(RwTimeoutinMilliSeconds);
                    try
                    {
                        this._vms = value;
                    }
                    finally
                    {
                        RwvMsLock.ReleaseWriterLock();
                    }
                }
                catch (ApplicationException)
                { }
            }
        }

        public ObservableCollection<VirtualMachine> VirtualMachines
        {
            get
            {                              
                return new ObservableCollection<VirtualMachine>(this.VMs);
            }
        }

        public VirtualMachine SelectedVirtualMachine
        {
            get
            {
                try
                {
                    RwSvmLock.AcquireReaderLock(RwTimeoutinMilliSeconds);
                    try
                    {
                        return this.DeployData.SelectedVirtualMachine;
                    }
                    finally
                    {
                        RwSvmLock.ReleaseReaderLock();
                    }
                }
                catch (ApplicationException)
                { }
                return null;
            }
            set
            {
                try
                {
                    RwSvmLock.AcquireWriterLock(RwTimeoutinMilliSeconds);
                    try
                    {
                        this.DeployData.SelectedVirtualMachine = value;
                    }
                    finally
                    {
                        RwSvmLock.ReleaseWriterLock();
                    }
                }
                catch (ApplicationException)
                { }

                this.OnPropertyChanged(PropertyNames.SelectedVirtualMachine);
                // trigger the UI thread
                this.CurrentDispatcher.Invoke((ThreadStart)delegate
                {
                    CallInvalidateRequerySuggested();
                });
                this.OnPropertyChanged(PropertyNames.IsUserInputComplete);
            }
        }

        public List<StorageAccount> StorageAccounts
        {
            get
            {
                try
                {
                    StorageAccountsReaderWriterLock.AcquireReaderLock(RwTimeoutinMilliSeconds);
                    try
                    {
                        return this._storageAccounts;
                    }
                    finally
                    {
                        StorageAccountsReaderWriterLock.ReleaseReaderLock();
                    }
                }
                catch (ApplicationException)
                { }
                return new List<StorageAccount>();
            }
            set
            {
                try 
                {
                    StorageAccountsReaderWriterLock.AcquireWriterLock(RwTimeoutinMilliSeconds);
                    try
                    {
                        this._storageAccounts = value;
                    }
                    finally
                    {
                        StorageAccountsReaderWriterLock.ReleaseWriterLock();
                    }
                }
                catch (ApplicationException)
                { }
                this.OnPropertyChanged(PropertyNames.CloudStorageAccounts);
            }
        }

        public ObservableCollection<StorageAccount> CloudStorageAccounts
        {
            get
            {
                return new ObservableCollection<StorageAccount>(this.StorageAccounts);
            }
        }


        public StorageAccount SelectedStorageAccount
        {
            get
            {
                try
                {
                    SaReaderWriterLock.AcquireReaderLock(RwTimeoutinMilliSeconds);
                    try
                    {
                        return this.DeployData.SelectedStorageAccount;
                    }
                    finally
                    {
                        SaReaderWriterLock.ReleaseReaderLock();
                    }
                }
                catch (ApplicationException)
                { }
                return null;
            }
            set
            {
                try
                {
                    SaReaderWriterLock.AcquireWriterLock(RwTimeoutinMilliSeconds);
                    try
                    {
                        this.DeployData.SelectedStorageAccount = value;
                    }
                    finally
                    {
                        SaReaderWriterLock.ReleaseWriterLock();
                    }
                }
                catch (ApplicationException)
                {
                    // return as there was an exception acquiring the lock
                    return;
                }

                ThreadPool.QueueUserWorkItem(this.SelectedStorageAccountCallBack, this.DeployData);

                this.OnPropertyChanged(PropertyNames.SelectedStorageAccount);
            }
        }

        /// <summary>
        /// populate storage containers
        /// </summary>
        /// <param name="context"></param>
        private void SelectedStorageAccountCallBack(Object context)
        {
            if (!this.DeployData.HasPayload)
            {
                // no app payload, return
                return;
            }

            if (this.DeployData.SelectedStorageAccount != null)
            {
                this.UpdateWaitMessage(DeployLandingPageViewModel.AzureWaitMessage);
             
                try
                {
                    StorageCredentials credentials = new StorageCredentials(this.SelectedStorageAccount.Name, this.SelectedStorageAccount.AccountKey);
                    CloudStorageAccount cloudStorageAccount = new CloudStorageAccount(credentials, new Uri(this.SelectedStorageAccount.AccountEndpoint), null, null);

                    CloudBlobClient cloudBlobClient = cloudStorageAccount.CreateCloudBlobClient();

                    List<StorageContainer> scs = new List<StorageContainer>();
                    foreach (CloudBlobContainer container in cloudBlobClient.ListContainers())
                    {
                        scs.Add(new StorageContainer(container));
                        
                        this.UpdateWaitMessage();
                    }
                    this.StorageContainers = scs;
                    this.SelectedStorageContainer = this._storageContainers.Count > 0 ? this._storageContainers[0] : null;
                    this._failedToConnectToStorageContainer = false;
                }
                catch (Exception)
                {
                    this._failedToConnectToStorageContainer = true;
                    this._storageContainers.Clear();
                    this.SelectedStorageContainer = null;
                }

                this._storageContainersCallBackCompleted = true;
            }
            else
            {
                this._storageContainers.Clear();
                this.SelectedStorageContainer = null;
            }

            // clear the message
            this.UpdateWaitMessage(string.Empty);

            this.OnPropertyChanged(PropertyNames.CloudStorageContainers);
            this.OnPropertyChanged(PropertyNames.SelectedStorageContainer);
            this.OnPropertyChanged(PropertyNames.IsUserInputComplete);
        }

        private List<StorageContainer> StorageContainers
        {
            get
            {
                try
                {
                    StorageContainerReaderWriterLock.AcquireReaderLock(RwTimeoutinMilliSeconds);
                    try
                    {
                        return this._storageContainers;
                    }
                    finally
                    {
                        StorageContainerReaderWriterLock.ReleaseReaderLock();
                    }
                }
                catch (ApplicationException)
                { }
                return new List<StorageContainer>();
            }
            set
            {
                try
                {
                    StorageContainerReaderWriterLock.AcquireWriterLock(RwTimeoutinMilliSeconds);
                    try
                    {
                        this._storageContainers = value;
                    }
                    finally
                    {
                        StorageContainerReaderWriterLock.ReleaseWriterLock();
                    }
                }
                catch (ApplicationException)
                { }
            }
        }

        public ObservableCollection<StorageContainer> CloudStorageContainers
        {
            get
            {
                return new ObservableCollection<StorageContainer>(this.StorageContainers);
            }
        }

        public StorageContainer SelectedStorageContainer
        {
            get
            {
                try
                {
                    RwSscLock.AcquireReaderLock(RwTimeoutinMilliSeconds);
                    try
                    {
                        return this.DeployData.SelectedStorageContainer;
                    }
                    finally
                    {
                        RwSscLock.ReleaseReaderLock();
                    }
                }
                catch (ApplicationException)
                {
                    // log this
                }
                return null;
               
            }
            set
            {
                try
                {
                    RwSscLock.AcquireWriterLock(RwTimeoutinMilliSeconds);
                    try
                    {
                        this.DeployData.SelectedStorageContainer = value;
                    }
                    finally
                    {
                        RwSscLock.ReleaseWriterLock();
                    }
                }
                catch (ApplicationException)
                {
                    // log this
                }

                this.OnPropertyChanged(PropertyNames.SelectedStorageContainer);
                // trigger the ui thread
                this.CurrentDispatcher.Invoke((ThreadStart)delegate
                {
                    CallInvalidateRequerySuggested();
                });
                this.OnPropertyChanged(PropertyNames.IsUserInputComplete);
            }
        }

        internal override bool IsUserInputComplete
        {
            get
            {
                if (!string.IsNullOrEmpty(AzureHelper.MSExtAppHandlerVersion) &&
                    string.IsNullOrEmpty(this.WaitMessage) &&
                    this.DeployData.CanConnect() &&
                    this.DeployData.SelectedCloudService != null &&
                    this.DeployData.SelectedVirtualMachine != null &&
                    (!this.DeployData.HasPayload || 
                     (this.DeployData.HasPayload &&
                      this.DeployData.SelectedStorageAccount != null &&
                      this.DeployData.SelectedStorageContainer != null)))
                {
                    return true;
                }
                return false;
            }
        }

        private void CallInvalidateRequerySuggested()
        {
            CommandManager.InvalidateRequerySuggested();
        }

        internal override bool PerformNextStep()
        {
            if (this.DeployData.SelectedVirtualMachine.GuestAgentStatus != null)
            {
                // the vm has an EH installed, check its status
                if (this.DeployData.SelectedVirtualMachine.GuestAgentStatus.Status != GuestAgentStatusEnum.Ready)
                {
                    System.Windows.MessageBox.Show("The Guest Agent installed on this Virtual Machine " + this.DeployData.SelectedVirtualMachine.Name +
                                    " is not a state to accept new configurations. Please check the Virtual Machine and " +
                                    "restart the Deployment wizard. ",
                                    DefinitionViewModel.ToolHeader, MessageBoxButton.OK, MessageBoxImage.Exclamation);
                    return false;
                }
            }
            else
            {
                System.Windows.MessageBox.Show("The Virtual Machine " + this.DeployData.SelectedVirtualMachine.Name +
                                    " is not reporting an Azure Guest Agent installation. Please check the Virtual Machine " +
                                    " and ensure that Azure Guest Agent is installed and running. ",
                                    DefinitionViewModel.ToolHeader, MessageBoxButton.OK, MessageBoxImage.Exclamation);
                return false;
            }

            if (this.DeployData.SelectedVirtualMachine.ResourceExtensionStatus != null)
            {
                // the vm has an EH installed, check its status
                if (this.DeployData.SelectedVirtualMachine.ResourceExtensionStatus.Status != HandlerAggregateStatus.Ready)
                {
                    System.Windows.MessageBox.Show("The Virtual Machine " + this.DeployData.SelectedVirtualMachine.Name +
                                    " is not a state to accept new configurations. Please check the Virtual Machine and " +
                                    "restart the Deployment wizard. ",
                                    DefinitionViewModel.ToolHeader, MessageBoxButton.OK, MessageBoxImage.Exclamation);
                    return false;
                }

                if (this.DeployData.SelectedVirtualMachine.ResourceExtensionStatus.ExtensionSettingStatus != null &&
                    this.DeployData.SelectedVirtualMachine.ResourceExtensionStatus.ExtensionSettingStatus.Status == HandlerStatusStates.Transitioning)
                {
                    System.Windows.MessageBox.Show("The Extension Handler installed on this Virtual Machine " + this.DeployData.SelectedVirtualMachine.Name +
                                    " is not a state to accept new configurations as it is transitioning. Please wait for some time and " +
                                    "restart the Deployment wizard. ",
                                    DefinitionViewModel.ToolHeader, MessageBoxButton.OK, MessageBoxImage.Exclamation);
                    return false;
                }
            }

            if (!this.DeployData.AreResourcesUploaded)
            {
                // only check local resources if the resources are not already uploaded
                return AzureHelper.UploadResExtResources(this.ResExtViewModel, this.DeployData, false);
            }
            // resources are already uploaded, check whether they exist in the storage containr
            return AzureHelper.CheckUploadedResExtResources(this.ResExtViewModel, this.DeployData);
        }

        internal override string GetSettings()
        {
            return string.Empty;
        }

        internal override string GetPowerShellScript()
        {
            return string.Empty;
        }

        public string Error
        {
            get { return String.Empty; }
        }

        public string this[string propertyName]
        {
            get
            {
                string error = null;
                switch (propertyName)
                {
                    case PropertyNames.SubscriptionId:
                        if (this.SubscriptionId == string.Empty ||
                            this._failedToConnectToCloudService || 
                            this._failedToConnectToStorageAccount)
                        {
                            error = "The VMRole Authoring Tool failed to connect to Microsoft Azure with the\n" +
                                "input Subscription Id and Management Certificate Thumbprint.\n" +
                                "Please ensure that the input values are valid.";
                        }
                        
                        break;
                    case PropertyNames.MgtCertThumbprint:
                        if (this.MgtCertThumbprint == string.Empty ||
                            this._failedToConnectToCloudService || 
                            this._failedToConnectToStorageAccount)
                        {
                            error = "The VMRole Authoring Tool failed to connect to Microsoft Azure with the\n" +
                                 "input Subscription Id and Management Certificate Thumbprint.\n" +
                                 "Please ensure that the input values are valid.";
                        }
                        else if (this.MgtCertThumbprint != null && this.DeployData.MgmtCertificate == null)
                        {
                            error = "Either the Management Certificate Thumbprint (as input) was invalid or\n" +
                                "the Management Certificate was not found on this machine.\n" +
                                "Please ensure that the Thumbprint is valid and that the Certificate\n" + 
                                "is installed on this machine.\n" +
                                "Hint: Try typing the Thumbprint value instead of pasting the value.";
                        }
                        break;
                    case PropertyNames.SelectedCloudService:
                        if (this._failedToConnectToCloudService)
                        {
                            error = "There was an error when trying to access list of Cloud Services\n" +
                                "for this Subscription. Please ensure that the Thumbprint and Certificate\n" +
                                "are valid and try again.";
                        }
                        else if (this._servicesCallBackCompleted &&
                                this.SelectedCloudService == null)
                        {
                            error = "Please select a Cloud Service.";
                        }
                        break;
                    case PropertyNames.SelectedStorageAccount:
                        if (this.DeployData.HasPayload)
                        {
                            if (this._failedToConnectToStorageAccount)
                            {
                                error = "There was an error when trying to access list of Storage Accounts\n" +
                                    "for this Subscription. Please ensure that the Thumbprint and Certificate\n" +
                                    "are valid and try again.";
                            }
                            else if (this._storageAccountsCallBackCompleted &&
                                     this.SelectedStorageAccount == null)
                            {
                                error = "Please select a Storage Account.";
                            }
                        }
                        break;
                    case PropertyNames.SelectedVirtualMachine:
                        if (this._failedToConnectToVirtualMachine)
                        {
                            error = "There was an error when trying to access list of Virtual Machines\n" +
                               "for this Cloud Service. Please ensure that the Thumbprint and Certificate\n" +
                               "are valid and try again.";
                        }
                        else if (this._vmsCallBackCompleted &&
                            this.SelectedVirtualMachine == null)
                        {
                            error = "Please select a Virtual Machine.";
                        }
                        break;
                    case PropertyNames.SelectedStorageContainer:
                        if (this.DeployData.HasPayload)
                        {
                            if (this._failedToConnectToStorageContainer)
                            {
                                error = "There was an error when trying to access list of Storage Containers\n" +
                                  "for this Storage Account. Please ensure that the Thumbprint and Certificate\n" +
                                  "are valid and try again.";
                            }
                            else if (this._storageContainersCallBackCompleted &&
                                this.SelectedStorageContainer == null)
                            {
                                error = "Please select a Storage Container.";
                            }
                        }
                        break;
                }

                return error;
            }
        }
    }

    public class CloudService
    {
        private readonly string name;
        public string Name
        {
            get { return this.name; }
        }
        public CloudService(string name)
        {
            this.name = name;
        }
    }

    public class StorageAccount
    {
        public string Name { get; private set; }

        internal string AccountKey { get; private set; }

        internal string AccountEndpoint { get; private set; }

        public StorageAccount(string name, string accountEndpoint, string accountKey)
        {
            this.Name = name;
            this.AccountEndpoint = accountEndpoint;
            this.AccountKey = accountKey;
        }
    }

    public class VirtualMachine
    {
        public string Name { get; private set; }

        private readonly string deploymentName;
        public string DeploymentName
        {
            get { return this.deploymentName; }
        }

        public string DisplayName
        {
            get { return this.Name + " (" + this.deploymentName + ")"; }
        }

        private GuestAgentStatus statusRoot;
        public GuestAgentStatus GuestAgentStatus
        {
            get { return this.statusRoot; }
            internal set { this.statusRoot = value; }
        }

        private ResourceExtensionStatus extStatus;
        public ResourceExtensionStatus ResourceExtensionStatus
        {
            get { return this.extStatus; }
            internal set { this.extStatus = value; }
        }

        public VirtualMachine(string name, string deploymentName)
        {
            this.Name = name;
            this.deploymentName = deploymentName;
        }
    }

    public class StorageContainer
    {
        private readonly CloudBlobContainer blobContainer;
        
        public string Name
        {
            get { return this.blobContainer.Name; }
        }

        public StorageContainer(CloudBlobContainer blobContainer)
        {
            this.blobContainer = blobContainer;
        }

        internal CloudBlobContainer CloudBlobContainer
        {
            get
            {
                return this.blobContainer;
            }
        }
    }
}